package com.livestocksolutions;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;

import com.livestocksolutions.interfaces.Observable;
import com.livestocksolutions.util.UpdateHistory;

/* -- Version History --
 *  1.0 : 12 July 2012 - Initial version.
 *  
 *  1.1 : 12 July 2012 - Changed Observation[] to ArrayList<Observation>
 *  
 *  1.2 : 16 July 2012 - Class now implements Serializable
 *  
 *  1.3 : 05 September 2012 - Added private long modifiedTime, private boolean modified and private final long id
 *                       public long getID(), public boolean getModified() and public long getModifiedDate()
 */

/**
 * This class allows any location to be represented.
 * A Location may be any type of storage location
 * @author Adam Parr
 * @version 1.3
 * 
 * @.modified 13 March 2012
 */
public class Location implements Observable{	
	/**
	 * Unique ID that identifies this Location
	 */
	protected final long id;	
	/**
	 * Keep track of any changes of this Location.
	 */
	protected HashMap<String,UpdateHistory> update = new HashMap<String,UpdateHistory>();
	/**
	 * A Name for this location
	 */
	protected String name;
	/**
	 * A description for this location
	 */
	protected String description;
	/**
	 * Any observations that are observed for this location are stored in this array
	 */
	protected ArrayList<Long> observations = new ArrayList<Long>();
	
	/**
	 * Constructor for creating an UsableItem
	 */
	public Location(){
		long tempID = Calendar.getInstance().getTimeInMillis();
		while(LivestockSolutions.getUsableItemByID(tempID)!=null){
			// Just in case multiple items of this type are made in the same millisecond
			tempID++;
		}
		id=tempID;
		description="";
		
		update.put("ID", new UpdateHistory(String.valueOf(id)));
		update.put("DESCRIPTION", new UpdateHistory(description));
	}
	
	/**
	 * Get the name of this Location
	 * @return Name of this location
	 */
	public String getName(){
		return name!=null?name:"";
	}
	
	/**
	 * Get the description of this location
	 * @return A description describing this location
	 */
	public String getDescription(){
		return description!=null?description:"";
	}
	
	/**
	 * Get an array of Observations that have been added to this Location
	 * @return An array of Observations
	 */
	@Override
	public Observation[] getObservations(){
		int size;
		Observation[] ob = new Observation[size = observations.size()];
		for (int x=0; x<size; x++) {
			ob[x] = LivestockSolutions.getObservationByID(observations.get(x));
		}
		return ob;
	}
	
	@Override
	public boolean removeObservation(Observation observation) {
		long oi=observation.getID();
		boolean value=observations.remove(oi);
		update.get("OBSERVATION").getValue(String.valueOf(observation.getID())).setValue(String.valueOf(!value));
		return value;
	}
	
	/**
	 * Set the name of this location
	 * @param name To new name to give this location
	 */
	public void setName(String name){
		this.name=name;
		update.put("NAME", new UpdateHistory(this.name));
	}
	
	/**
	 * Set the description for this location
	 * @param description A new description to give to this location
	 */
	public void setDescription(String description){
		this.description=description;
		update.put("DESCRIPTION", new UpdateHistory(this.description));
	}
	/**
	 * Add an observation to this location
	 * @param observation The observation to add
	 */
	public void addObservation(Observation observation){
		if(!observations.contains(observation.getID())){
			observations.add(observation.getID());
			UpdateHistory observationList = update.get("OBSERVATION")!=null? update.get("OBSERVATION"):new UpdateHistory();
			observationList.setValues(String.valueOf(observation.getID()),new UpdateHistory("true"));
			update.put("OBSERVATION", observationList);
		}
		observation.setOwner(this);
		LivestockSolutions.addObservation(observation);
	}
	
	/**
	 * Get the unique id of this Location
	 * @return the id of this Location
	 */
	public long getID(){
		return id;
	}
	
	/**
	 * Updates this Object with the updated values from the different version.
	 * @param changes Location that represents this Object in a different version.
	 * @return <tt>true</tt> if changes is not null and changes does not equal this object, <tt>false</tt> otherwise.
	 */
	public boolean update(Location changes){
		if(changes!=null && this!=changes){
			if(updateBefore("DESCRIPTION",changes)){
				description=changes.update.get("DESCRIPTION").getValue();
				update.put("DESCRIPTION", new UpdateHistory(description));
			}
			
			if(updateBefore("NAME",changes)){
				name=(changes.update.get("NAME").getValue());
				update.put("NAME", new UpdateHistory(name));
			}
			
			// Associative ArrayLists (Only Long ID)
			// Add Observations that may be new
			if(changes.update.get("OBSERVATION")!=null){
				for(String key : changes.update.get("OBSERVATION").toKeyArray()){
					if(update.get("OBSERVATION")==null || update.get("OBSERVATION").getValue(key)==null || !update.get("OBSERVATION").getValue(key).getDate().after(changes.update.get("OBSERVATION").getValue(key).getDate())){
						UpdateHistory observationList = update.get("OBSERVATION")!=null?update.get("OBSERVATION"):new UpdateHistory();
						if(changes.update.get("OBSERVATION").getValue(key).equals("false")){
							// Remove
							observations.remove(Long.parseLong(key));
							observationList.setValues(key,new UpdateHistory("false"));
						}else{
							// Add
							if(!observations.contains(Long.parseLong(key))){
								observations.add(Long.parseLong(key));
							}
							observationList.setValues(key,new UpdateHistory("true"));
						}
						update.put("OBSERVATION", observationList);
					}
				}
			}
			return true;
		}
		return false;
	}
	
	/**
	 * Checks to see if the modification date of the item at this position is before the changed value
	 * @param key The String key at which to compare the modification dates of the modified objects.
	 * @param changes The Location that represents this Location on another system.
	 * @return <tt>true</tt> if the old value was modified before the changed value. <tt>false</tt> otherwise.
	 */
	protected boolean updateBefore(String key,Location changes){
		if(this.update.get(key)==null && changes.update.get(key)!=null){
			return true;
		}else if(changes.update.get(key)==null){
			return false;
		}
		return !this.update.get(key).getDate().after(changes.update.get(key).getDate());
	}
}
